package org.sdp.pearproject.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.RequestFacade;
import org.sdp.pearproject.dto.request.BaseRequest;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

@Slf4j
public class JavaBeansUtil {

    public static String getAjaxString(String inputString) {
        String str = getCamelCaseString(inputString, false);
        StringBuilder sb = new StringBuilder();

        boolean nextUpperCase = false;
        for (int i = 0; i < inputString.length(); i++) {
            char c = inputString.charAt(i);
            if (Character.isUpperCase(c)) {
                if (i > 0) {
                    sb.append("_");
                }
                sb.append(Character.toLowerCase(c));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();

    }

    public static String getCamelCaseString(String inputString,
                                            boolean firstCharacterUppercase) {
        StringBuilder sb = new StringBuilder();

        boolean nextUpperCase = false;
        for (int i = 0; i < inputString.length(); i++) {
            char c = inputString.charAt(i);

            switch (c) {
                case '_':
                case '-':
                case '@':
                case '$':
                case '#':
                case ' ':
                case '/':
                case '&':
                    if (sb.length() > 0) {
                        nextUpperCase = true;
                    }
                    break;

                default:
                    if (nextUpperCase) {
                        sb.append(Character.toUpperCase(c));
                        nextUpperCase = false;
                    } else {
                        sb.append(Character.toLowerCase(c));
                    }
                    break;
            }
        }

        if (firstCharacterUppercase) {
            sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
        }

        return sb.toString();
    }

    public static Field getField(Class clz, String name) {
        return getField(clz, name, true);
    }

    public static Field getField(Class clz, String name, Boolean findSuper) {
        String camelCaseName = getCamelCaseString(name, false);
        while(clz != null) {
            Field[] fields = clz.getDeclaredFields();
            for(Field field: fields) {
                if (name.equals(field.getName())
                        || camelCaseName.equals(field.getName())
                ) {
                    field.setAccessible(true);
                    return field;
                }
                if (field.isAnnotationPresent(JSONField.class)) {
                    JSONField f = field.getAnnotation(JSONField.class);
                    if (!StringUtils.isEmpty(f.name())) {
                        if (name.equals(f.name())
                                || camelCaseName.equals(f.name())
                        ) {
                            field.setAccessible(true);
                            return field;
                        }
                    }
                }
            }
            if (!findSuper) {
                break;
            }
            clz = clz.getSuperclass();
        }
        return null;
    }

    public static Field[] getFields(Class clz) {
        return getFields(clz, true);
    }

    public static Field[] getFields(Class clz, Boolean findSuper) {
        List<Field> fieldList = new ArrayList<>();
        getFields(clz, findSuper, fieldList);
        Field[] fields = new Field[fieldList.size()];
        for(int i = 0; i < fieldList.size(); i++) {
            Field field = fieldList.get(i);
            fields[i] = field;
        }
        return fields;
    }

    public static void getFields(Class clz, Boolean findSuper, List<Field> fieldList) {
        while(clz != null) {
            Field[] fields = clz.getDeclaredFields();
            for(Field field: fields) {
                if (field.isAnnotationPresent(JSONField.class)) {
                    field.setAccessible(true);
                    fieldList.add(field);
                }
            }
            if (!findSuper) {
                break;
            }
            clz = clz.getSuperclass();
        }
    }

    public static Method getMethod(Class clz, String name) {
        return getMethod(clz, name, true);
    }

    public static Method getMethod(Class clz, String name, Boolean findSuper) {
        String camelCaseName = JavaBeansUtil.getCamelCaseString(name, false);

        while (clz != null) {
            for(Method method: clz.getDeclaredMethods()) {
                if (name.equals(method.getName())) {
                    method.setAccessible(true);
                    return method;
                }
                if (method.isAnnotationPresent(JSONField.class)) {
                    JSONField f = method.getAnnotation(JSONField.class);
                    if (!StringUtils.isEmpty(f.name())) {
                        if (name.equals(f.name())
                                || camelCaseName.equals(f.name())
                        ) {
                            method.setAccessible(true);
                            return method;
                        }
                    }
                }
            }
            if (!findSuper) {
                break;
            }
            clz = clz.getSuperclass();
        }
        return null;
    }

    public static void copyBooleanProperty(Object source, Object target, Field sourceField, Field targetField) throws Exception {
        if (source == null || target == null || sourceField == null || targetField == null) {
            return;
        }
        if (Boolean.class.equals(sourceField.getType())) {
            return;
        }
        if (sourceField != null) {
            Object srcV = sourceField.get(source);
            if (srcV == null) {
                return;
            }
            if ("0".equals(srcV+"")) {
                targetField.set(target, new Boolean(false));
            } else {
                targetField.set(target, new Boolean(true));
            }
        }
    }

    public static List<String> fixStringArray(List<String> list) {
        if (!CollectionUtils.isEmpty(list)
                && list.get(0).startsWith("[")
                && list.get(list.size() - 1).endsWith("]")
        ) {
            try {
                String strs = "";
                for(String str: list) {
                    if (strs.length() > 0) {
                        strs += ",";
                    }
                    strs += str;
                }
                JSONArray jsonArray = JSON.parseArray(strs);
                list = new ArrayList<>();
                for(int i = 0; i < jsonArray.size(); i++) {
                    list.add(jsonArray.getString(i));
                }
            }catch(Exception ex) {
                ex.printStackTrace();
            }
        }
        return list;
    }

    public static void copyIntegerProperty(Object source, Object target, Field sourceField, Field targetField) throws Exception {
        if (source == null || target == null || sourceField == null || targetField == null) {
            return;
        }
        if (Integer.class.equals(sourceField.getType())) {
            return;
        }
        if (sourceField != null) {
            Object srcV = sourceField.get(source);
            if (srcV == null) {
                return;
            }
            if (Boolean.class.equals(sourceField.getType())) {
                Boolean v = (Boolean) srcV;
                if (v) {
                    targetField.set(target, new Integer(1));
                } else {
                    targetField.set(target, new Integer(0));
                }
            } else if (Byte.class.equals(sourceField.getType())) {
                Byte v = (Byte) srcV;
                targetField.set(target, new Integer(v.intValue()));
            } else if (Long.class.equals(sourceField.getType())) {
                Long v = (Long) srcV;
                targetField.set(target, new Integer(v.intValue()));
            } else {
                targetField.set(target, new Integer(srcV+""));
            }
        }
    }

    public static void copyLongProperty(Object source, Object target, Field sourceField, Field targetField) throws Exception {
        if (source == null || target == null || sourceField == null || targetField == null) {
            return;
        }
        if (Long.class.equals(sourceField.getType())) {
            return;
        }
        if (sourceField != null) {
            Object srcV = sourceField.get(source);
            if (srcV == null) {
                return;
            }
            if (Boolean.class.equals(sourceField.getType())) {
                Boolean v = (Boolean) srcV;
                if (v) {
                    targetField.set(target, new Long(1));
                } else {
                    targetField.set(target, new Long(0));
                }
            } else if (Byte.class.equals(sourceField.getType())) {
                Byte v = (Byte) srcV;
                targetField.set(target, new Long(v.intValue()));
            } else if (Integer.class.equals(sourceField.getType())) {
                Integer v = (Integer) srcV;
                targetField.set(target, new Long(v.intValue()));
            } else {
                targetField.set(target, new Long(srcV+""));
            }
        }
    }

    public static void copyByteProperty(Object source, Object target, Field sourceField, Field targetField) throws Exception {
        if (source == null || target == null || sourceField == null || targetField == null) {
            return;
        }
        if (Byte.class.equals(sourceField.getType())) {
            return;
        }
        if (sourceField != null) {
            Object srcV = sourceField.get(source);
            if (srcV == null) {
                return;
            }
            if (Boolean.class.equals(sourceField.getType())) {
                Boolean v = (Boolean) srcV;
                if (v) {
                    targetField.set(target, new Byte((byte)1));
                } else {
                    targetField.set(target, new Byte((byte)0));
                }
            } else if (Integer.class.equals(sourceField.getType())) {
                Integer v = (Integer) srcV;
                targetField.set(target, new Byte(v.byteValue()));
            } else if (Long.class.equals(sourceField.getType())) {
                Long v = (Long) srcV;
                targetField.set(target, new Byte(v.byteValue()));
            } else {
                targetField.set(target, new Byte(srcV+""));
            }
        }
    }

    public static void copyStringProperty(Object source, Object target, Field sourceField, Field targetField) throws Exception {
        if (source == null || target == null || sourceField == null || targetField == null) {
            return;
        }
        if (String.class.equals(sourceField.getType())) {
            return;
        }
        if (sourceField != null) {
            Object srcV = sourceField.get(source);
            if (srcV == null) {
                return;
            }
            if (Boolean.class.equals(sourceField.getType())) {
                Boolean v = (Boolean) srcV;
                if (v) {
                    targetField.set(target, new String("1"));
                } else {
                    targetField.set(target, new String("0"));
                }
            } else {
                targetField.set(target, JSONObject.toJSONString(srcV, SerializerFeature.DisableCircularReferenceDetect));
            }
        }
    }

    public static Object clone(Object v) {
        return clone(v, 0);
    }

    public static Object clone(Object v, int deep) {
        Object destV = null;
        if (v == null) {
            destV = null;
        } else if (v instanceof JSONObject) {
            if (deep > 3) {
                destV = v;
            } else {
                JSONObject srcJSONObject = (JSONObject) v;
                JSONObject destJSONObject = new JSONObject();
                for(String key: srcJSONObject.keySet()) {
                    destJSONObject.put(key, clone(srcJSONObject.get(key), deep + 1));
                }
                destV = destJSONObject;
            }
        } else if (v instanceof TreeMap) {
            if (deep > 3) {
                destV = v;
            } else {
                Map srcJSONObject = (Map) v;
                Map destJSONObject = new TreeMap();
                for(Object key: srcJSONObject.keySet()) {
                    destJSONObject.put(key, clone(srcJSONObject.get(key), deep + 1));
                }
                destV = destJSONObject;
            }
        } else if (v instanceof HashMap) {
            if (deep > 3) {
                destV = v;
            } else {
                Map srcJSONObject = (Map) v;
                Map destJSONObject = new HashMap();
                for(Object key: srcJSONObject.keySet()) {
                    destJSONObject.put(key, clone(srcJSONObject.get(key), deep + 1));
                }
                destV = destJSONObject;
            }
        } else if (v instanceof JSONArray) {
            if (deep > 3) {
                destV = v;
            } else {
                JSONArray srcJSONArray = (JSONArray) v;
                JSONArray destJSONArray = new JSONArray();
                for(int i = 0; i < srcJSONArray.size(); i++) {
                    destJSONArray.add(clone(srcJSONArray.get(i), deep + 1));
                }
                destV = destJSONArray;
            }
        } else if (v instanceof ArrayList) {
            if (deep > 3) {
                destV = v;
            } else {
                List srcJSONArray = (List) v;
                List destJSONArray = new ArrayList();
                for(int i = 0; i < srcJSONArray.size(); i++) {
                    destJSONArray.add(clone(srcJSONArray.get(i), deep + 1));
                }
                destV = destJSONArray;
            }
        } else if (v instanceof Boolean) {
            destV = new Boolean((Boolean)v);
        } else if (v instanceof Byte) {
            destV = new Byte((Byte) v);
        } else if (v instanceof Integer) {
            destV = new Integer((Integer) v);
        } else if (v instanceof Long) {
            destV = new Long((Long) v);
        } else if (v instanceof String) {
            destV = new String((String) v);
        } else {
            destV = v;
        }
        return destV;
    }

    public static void copyCollectionProperty(Object source, Object target, Field sourceField, Field targetField) throws Exception {
        if (source == null || target == null || sourceField == null || targetField == null) {
            return;
        }
        if (!targetField.getType().equals(sourceField.getType())) {
            return;
        }
        if (sourceField != null) {
            Object srcV =  sourceField.get(source);
            if (srcV == null) {
                return;
            }
            targetField.set(target, clone(srcV));
        }
    }

    public static void copyProperties(Object source, Object target) throws Exception {
        if (source == null || target == null) {
            return;
        }
        copyProperties(source, target, null, null);
    }

    public static void copyProperties(Object source, Object target, Map<String, Field> sourceFieldSet, Map<String, Field> targetFieldSet) throws Exception {
        if (source == null || target == null) {
            return;
        }
        BeanUtils.copyProperties(source, target);
        Field[] fields = getFields(target.getClass(), true);
        for(Field field: fields) {
            if (Boolean.class.equals(field.getType())) {
                copyBooleanProperty(source, target, getField(source.getClass(), field.getName()), field);
            }
            if (Byte.class.equals(field.getType())) {
                copyByteProperty(source, target, getField(source.getClass(), field.getName()), field);
            }
            if (Integer.class.equals(field.getType())) {
                copyIntegerProperty(source, target, getField(source.getClass(), field.getName()), field);
            }
            if (Long.class.equals(field.getType())) {
                copyLongProperty(source, target, getField(source.getClass(), field.getName()), field);
            }
            if (String.class.equals(field.getType())) {
                copyStringProperty(source, target, getField(source.getClass(), field.getName()), field);
            }
            if (JSONObject.class.equals(field.getType())
                    || TreeMap.class.equals(field.getType())
                    || HashMap.class.equals(field.getType())
                    || JSONArray.class.equals(field.getType())
                    || ArrayList.class.equals(field.getType())
            ) {
                copyCollectionProperty(source, target, getField(source.getClass(), field.getName()), field);
            }
        }
    }

    public static void fixParameters(HttpServletRequest httpServletRequest, Class baseRequestClass) {
        String url = httpServletRequest.getRequestURL().toString();

        //force parse parameter first!
        if (httpServletRequest.getParameterNames() != null) {
            try {
                Map<String, Field> fieldMap = new HashMap<>();
                try {
                    Field[] fields = baseRequestClass.getDeclaredFields();
                    for(Field field: fields) {
                        try {
                            String name = field.getName();
                            if (field.isAnnotationPresent(JsonProperty.class)) {
                                JsonProperty jsonProperty = field.getAnnotation(JsonProperty.class);
                                if (StringUtils.hasText(jsonProperty.value())) {
                                    name = jsonProperty.value();
                                }
                            } else if (field.isAnnotationPresent(JSONField.class)) {
                                JSONField jsonField = field.getAnnotation(JSONField.class);
                                if (StringUtils.hasText(jsonField.name())) {
                                    name = jsonField.name();
                                }
                            }
                            if (!fieldMap.containsKey(name)) {
                                field.setAccessible(true);
                                fieldMap.put(name, field);
                            }
                        }catch (Exception ex) {

                        }
                    }
                }catch(Exception ex) {

                }

                Object target = httpServletRequest;
                Field hField = JavaBeansUtil.getField(httpServletRequest.getClass(), "request");
                if (hField != null) {
                    target = hField.get(target);
                    hField = null;
                    if (target != null) {
                        hField = JavaBeansUtil.getField(target.getClass(), "coyoteRequest");
                    }
                }
                if (hField != null) {
                    target = hField.get(target);
                    hField = null;
                    if (target != null) {
                        hField = JavaBeansUtil.getField(target.getClass(), "parameters");
                    }
                }
                if (hField != null) {
                    target = hField.get(target);
                    hField = null;
                    if (target != null) {
                        hField = JavaBeansUtil.getField(target.getClass(), "paramHashValues");
                    }
                }
                if (hField != null) {
                    target = hField.get(target);
                    if (target != null) {
                        Map<String, ArrayList<String>> paramHashValues = (Map<String, ArrayList<String>>) target;
                        Map<String, String> newParamHashValues = new HashMap<>();

                        for(String key: new String[]{}) {
                            //删除无效的参数。例如xxx接口同时传递了member_code和memberCode，其中一个有数据，另外一个无数据
                            paramHashValues.remove(key);
                        }

                        for(String key: paramHashValues.keySet()) {
                            String ajaxKey = JavaBeansUtil.getAjaxString(key);
                            String camelKey = JavaBeansUtil.getCamelCaseString(ajaxKey, false);
                            Field field = fieldMap.get(key);
                            if (!key.equals(ajaxKey) && fieldMap.containsKey(ajaxKey)) {
                                if(!newParamHashValues.containsKey(ajaxKey)) {
                                    newParamHashValues.put(ajaxKey, key);
                                }
                                field = fieldMap.get(ajaxKey);
                            }
                            if (!key.equals(camelKey) && fieldMap.containsKey(camelKey)) {
                                if (!newParamHashValues.containsKey(camelKey)) {
                                    newParamHashValues.put(camelKey, key);
                                }
                                field = fieldMap.get(camelKey);
                            }
                            if (field == null) {
                                log.error("miss parameter: "+key+" for class: "+baseRequestClass.getName());
                                continue;
                            }
                            if (!newParamHashValues.containsKey(field.getName())) {
                                newParamHashValues.put(field.getName(), key);
                            }

                            ArrayList<String> values = paramHashValues.get(key);
                            if (!CollectionUtils.isEmpty(values) && values.size() == 1) {
                                String value = values.get(0);
                                if (Byte.class.equals(field.getType())) {
                                    if ("true".equals(value)) {
                                        values.remove(0);
                                        values.add("1");
                                    } else if ("false".equals(value)) {
                                        values.remove(0);
                                        values.add("0");
                                    }
                                } else if (List.class.equals(field.getType())) {
                                    if ("java.util.List<java.lang.String>".equals(field.getGenericType().getTypeName())) {
                                    }
                                }
                            }
                        }
                        for(String key: newParamHashValues.keySet()) {
                            paramHashValues.put(key, paramHashValues.get(newParamHashValues.get(key)));
                        }

                    }
                }

            }catch(Exception ex) {
                ex.printStackTrace();
            }

            log.info("request url: {}, param: {}", url, JSON.toJSONString(httpServletRequest.getParameterMap(), SerializerFeature.DisableCircularReferenceDetect));

        }
    }

    public static JSONObject parseObject(String v) {
        return JSON.parseObject(v, Feature.AllowUnQuotedFieldNames, Feature.AllowSingleQuotes);
    }

    public static String toJSONString(Object request) {
        return JSON.toJSONString(request, SerializerFeature.DisableCircularReferenceDetect);
    }

}
